// Copyright (C) 2011 by Christopher Diggins
// Usage is permitted under the terms of the MIT License 1.0. 	

/** 
	\file
	Contains numerous tests. 
*/

#include <stdio.h>
#include <time.h>

#include "debugger.h"

int test(int cond, char* text)
{
	if (!cond) 
		printf("Test FAILED: %s\n", text);
	else		
		printf("Test PASSED: %s\n", text);
	return cond;
}

#define TEST(X) test(X, #X)

void timer_init(time_t* curtime) {
	*curtime = clock();
}

void timer_output(time_t* curtime) {
	time_t e = clock() - *curtime;
	printf("Time elapsed = %d msec\n", (int)e);
	*curtime = clock();
}

void check_vm_top_int(VM* vm, int n)
{
	TEST(vm_pull_int() == n);
}

void output_table_stats(Val* table) {
	printf("Table load %d / 256\n", table_load(table));
	printf("Number of items %d\n", table_count(table));
	printf("Table size %d\n", table_size(table));
}

void simple_vm_test(byte* input, int size, int output)
{
	VM* vm = (VM*)calloc(1, sizeof(VM));	
	vm_init(vm);
	vm_load_code(vm, input, size);
	printf("Running test, expected %d\nCode\n", output);
	print_code(vm);
	do
	{
		print_vm_state(vm);
	}
	while (!vm_next(vm));
	check_vm_top_int(vm, output);
	vm_free(vm);
}

#define VM_TEST(OUTPUT) simple_vm_test(input, sizeof(input), OUTPUT);

void simple_vm_tests(VM* vm)
{
	printf("Testing Simple VM operations\n");
	{ vm_push_int(5); check_vm_top_int(vm, 5); }
	{ byte input[] = { _zero, _ret }; VM_TEST(0); }
	{ byte input[] = { _one, _ret }; VM_TEST(1); }
	{ byte input[] = { _two, _ret }; VM_TEST(2); } 
	{ byte input[] = { _neg1, _ret }; VM_TEST(-1); } 
	{ byte input[] = { _byte, 5, _ret }; VM_TEST(5); }
	{ byte input[] = { _byte, 5, _ret }; VM_TEST(5); }
	{ byte input[] = { _byte, 5, _inc, _ret }; VM_TEST(6); }
	{ byte input[] = { _byte, 5, _dec, _ret }; VM_TEST(4); }
	{ byte input[] = { _byte, 5, _noop, _ret }; VM_TEST(5); }
	{ byte input[] = { _short, 5, 0, _ret }; VM_TEST(5); }
	{ byte input[] = { _long, 0, 1, 0, 0, _ret }; VM_TEST(256); }
	{ byte input[] = { _byte, 5, _byte, 7, _pop, _ret }; VM_TEST(5); }
	{ byte input[] = { _byte, 5, _byte, 7, _swap, _pop, _ret }; VM_TEST(7); }
	{ byte input[] = { _byte, 5, _byte, 7, _swap, _swap, _ret }; VM_TEST(7); }
	{ byte input[] = { _byte, 5, _byte, 7, _add, _ret }; VM_TEST(12); }
	{ byte input[] = { _byte, 5, _byte, 7, _sub, _ret }; VM_TEST(2); }
	{ byte input[] = { _byte, 5, _byte, 7, _mul, _ret }; VM_TEST(35); }
	{ byte input[] = { _byte, 5, _byte, 7, _mod, _ret }; VM_TEST(2); }
	{ byte input[] = { _byte, 7, _byte, 42, _div, _ret }; VM_TEST(6); }
	{ byte input[] = { _byte, 5, _byte, 7, _swap, _sub, _ret }; VM_TEST(-2); }
	{ byte input[] = { _byte, 5, _dup, _pop, _ret }; VM_TEST(5); }
	{ byte input[] = { _byte, 5, _dup, _add, _ret }; VM_TEST(10); }
}

void array_tests(VM* vm)
{
	uint i, n;
	Val* v;
	Array a;
	Val** begin;
	Val** end;

	printf("Testing arrays\n");

	array_init(Val*, &a);
	TEST(array_count(&a) == 0);

	// Check that no corruption happens as we add values
	for (i=0; i < 100; ++i) {
		v = val_from_int(i);
		array_push(Val*, &a, v);
		TEST(array_count(&a) == i + 1);
		v = array_at(Val*, &a, i);
		n = val_to_int(v);
		TEST(n == i);
	}
	TEST(array_count(&a) == 100);	

	// Check that all the values are as expected
	// This does it using "at"
	for (i=0; i < 100; ++i) {
		v = array_at(Val*, &a, i);
		n = val_to_int(v);
		TEST(n == i);
	}

	// Check again by using begin/end iterators
	i = 0;
	begin = array_begin(Val*, &a);
	end = array_begin(Val*, &a);
	for (; begin != end; ++begin) {
		v = *begin;
		n = val_to_int(v);
		TEST(n == i++);
	}

	// Remove values from the stack.
	for (i=100; i > 0; --i) {
		v = array_top(Val*, &a);
		array_pop(&a);
		n = val_to_int(v);
		TEST(n == i - 1);
		TEST(array_count(&a) == n);
	}
	TEST(array_count(&a) == i);
	array_clear(&a);
}

void test_mem()
{
	VM* vm = (VM*)calloc(1, sizeof(VM));
	vm_init(vm);
	printf("Testing memory\n");
	print_vm_state(vm);
	vm_push_int(42);
	print_vm_state(vm);
	vm_pull();
	vm_push(obj_new());
	vm_free(vm);
	free(vm);
}

void print_key_value(KeyVal* kv) {
	char* occupied = table_slot_occupied(kv) ? "occupied" : "unoccupied";
	printf("Table entry (%s) : key %d value %x\n", occupied, kv->key, kv->val);
}

void table_tests(VM* vm, uint table_test_size, uint print_entries, uint show_timer, uint show_stats)
{
	time_t curtime;
	uint i, nv;
	Val table;
	KeyVal* kv;
	printf("Testing tables\n");
	table_init(&table);
	timer_init(&curtime);

	for (i=0; i < table_test_size; ++i) {
		kv = table_find(&table, i);
		TEST(!table_slot_occupied(kv));
		table_add(&table, i, val_from_int(i + 3));
		kv = table_find(&table, i);
		TEST(table_slot_occupied(kv) != NULL);
		nv = val_to_int(kv->val);
		TEST(nv == i + 3);
	}

	for (i=0; i < (size_t)table_size(&table); ++i) {
		kv = &table_at(&table, i);
		
		if (print_entries)
			print_key_value(kv);
		
		if (table_slot_occupied(kv))
		{
			TEST(kv->key >= 0);
			TEST(kv->key < table_test_size);
			nv = val_to_int(kv->val);
			TEST(nv == kv->key + 3);
		}
	}

	for (i=0; i < table_test_size; ++i) {
		kv = table_find(&table, i);
		TEST(table_slot_occupied(kv) != NULL);
		TEST(kv->key == i);
		TEST(val_to_int(kv->val) == i + 3);
	}	

	if (show_timer)
		timer_output(&curtime);

	if (show_stats)
		output_table_stats(&table);

	table_free(&table);
}

void vm_init_free_test()
{
	int i;
	VM* vm;
	vm = (VM*)calloc(1, sizeof(VM));
	for (i = 0; i < 100; ++i) {
		vm_init(vm);
		vm_free(vm);
	}
}

void run_tests()
{	
	VM* vm;
	int thousand = 1000;
	int million = thousand * thousand;
	vm_init_free_test();
	vm = (VM*)calloc(1, sizeof(VM));
	vm_init_free_test(vm);
	vm_init(vm);
	simple_vm_tests(vm);
	array_tests(vm);
	table_tests(vm, 10, 0, 1, 1);
	//table_tests(10 * million, 0, 1, 1);
	test_mem();
	vm_free(vm);
	free(vm);
}
